package com.saasovation.collaboration.port.adapter.spring;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.saasovation.collaboration.application.calendar.CalendarApplicationService;
import com.saasovation.collaboration.application.calendar.CalendarEntryApplicationService;
import com.saasovation.collaboration.application.calendar.CalendarEntryQueryService;
import com.saasovation.collaboration.application.calendar.CalendarQueryService;
import com.saasovation.collaboration.application.forum.*;
import com.saasovation.collaboration.domain.model.DomainRegistry;
import com.saasovation.collaboration.domain.model.calendar.CalendarEntryRepository;
import com.saasovation.collaboration.domain.model.calendar.CalendarIdentityService;
import com.saasovation.collaboration.domain.model.calendar.CalendarRepository;
import com.saasovation.collaboration.domain.model.collaborator.CollaboratorService;
import com.saasovation.collaboration.domain.model.forum.DiscussionRepository;
import com.saasovation.collaboration.domain.model.forum.ForumIdentityService;
import com.saasovation.collaboration.domain.model.forum.ForumRepository;
import com.saasovation.collaboration.domain.model.forum.PostRepository;
import com.saasovation.collaboration.port.adapter.event.FollowStoreEventDispatcher;
import com.saasovation.collaboration.port.adapter.messaging.SpringRabbitMQEventDispatcher;
import com.saasovation.collaboration.port.adapter.persistence.view.*;
import com.saasovation.common.event.sourcing.EventStore;
import com.saasovation.common.port.adapter.messaging.Exchanges;
import com.saasovation.common.port.adapter.messaging.rabbitmq.ConnectionSettings;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.JacksonUtils;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreatorFactory;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.ParsedSql;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.support.JdbcTransactionManager;
import org.springframework.lang.NonNull;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;
import java.sql.ResultSet;

@Configuration
public class ApplicationConfiguration {

    @Bean
    public DataSource dataSource(Environment environment) {
        return DataSourceBuilder.create()
                .type(HikariDataSource.class)
                .url(environment.getRequiredProperty("jdbc.url"))
                .username(environment.getRequiredProperty("jdbc.username"))
                .password(environment.getRequiredProperty("jdbc.password"))
                .driverClassName(environment.getRequiredProperty("jdbc.driverClassName"))
                .build();
    }

    @Bean
    public PlatformTransactionManager platformTransactionManager(DataSource dataSource) {
        JdbcTransactionManager txManager = new JdbcTransactionManager();
        txManager.setDataSource(dataSource);
        return txManager;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }

    @Bean
    public NamedParameterJdbcTemplate namedParameterJdbcTemplate(DataSource dataSource) {
        return new NamedParameterJdbcTemplate(dataSource) {
            @Override
            @NonNull
            protected PreparedStatementCreatorFactory getPreparedStatementCreatorFactory(
                    @NonNull ParsedSql parsedSql,
                    @NonNull SqlParameterSource paramSource) {
                PreparedStatementCreatorFactory creatorFactory =
                        super.getPreparedStatementCreatorFactory(parsedSql, paramSource);
                creatorFactory.setResultSetType(ResultSet.TYPE_SCROLL_SENSITIVE);
                return creatorFactory;
            }
        };
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        ObjectMapper objectMapper = JacksonUtils.enhancedObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
        rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter(objectMapper));
        return rabbitTemplate;
    }

    @Bean
    public ConnectionSettings connectionSettings(Environment environment) {
        return ConnectionSettings.instance(
                environment.getProperty("spring.rabbitmq.host", "localhost"),
                Integer.parseInt(environment.getProperty("spring.rabbitmq.port", "5672")),
                environment.getProperty("spring.rabbitmq.virtual-host", "/"),
                environment.getProperty("spring.rabbitmq.username"),
                environment.getProperty("spring.rabbitmq.password"));
    }

    @Bean
    public Exchange collaborationExchange() {
        return new FanoutExchange(Exchanges.COLLABORATION_EXCHANGE_NAME);
    }

    @Bean
    public FanoutExchange agilePmExchange() {
        return new FanoutExchange(Exchanges.AGILE_PM_EXCHANGE_NAME);
    }

    @Bean
    public FanoutExchange identityAccessExchange() {
        return new FanoutExchange(Exchanges.IDENTITY_ACCESS_EXCHANGE_NAME);
    }

    @Bean
    public CalendarApplicationService calendarApplicationService(
            CalendarRepository aCalendarRepository,
            CalendarEntryRepository aCalendarEntryRepository,
            CalendarIdentityService aCalendarIdentityService,
            CollaboratorService aCollaboratorService) {
        return new CalendarApplicationService(aCalendarRepository,
                aCalendarEntryRepository,
                aCalendarIdentityService,
                aCollaboratorService);
    }

    @Bean
    public CalendarEntryQueryService calendarEntryQueryService(NamedParameterJdbcTemplate jdbcTemplate) {
        return new CalendarEntryQueryService(jdbcTemplate);
    }

    @Bean
    public CalendarEntryApplicationService calendarEntryApplicationService(
            CalendarEntryRepository aCalendarEntryRepository,
            CollaboratorService aCollaboratorService) {
        return new CalendarEntryApplicationService(aCalendarEntryRepository,
                aCollaboratorService);
    }

    @Bean
    public CalendarQueryService calendarQueryService(NamedParameterJdbcTemplate jdbcTemplate) {
        return new CalendarQueryService(jdbcTemplate);
    }

    @Bean
    public DiscussionApplicationService discussionApplicationService(
            DiscussionRepository aDiscussionRepository,
            ForumIdentityService aForumIdentityService,
            PostRepository aPostRepository,
            CollaboratorService aCollaboratorService) {
        return new DiscussionApplicationService(aDiscussionRepository,
                aForumIdentityService,
                aPostRepository,
                aCollaboratorService);
    }

    @Bean
    public DiscussionQueryService discussionQueryService(NamedParameterJdbcTemplate jdbcTemplate) {
        return new DiscussionQueryService(jdbcTemplate);
    }

    @Bean
    public ForumApplicationService forumApplicationService(
            ForumQueryService aForumQueryService,
            ForumRepository aForumRepository,
            ForumIdentityService aForumIdentityService,
            DiscussionQueryService aDiscussionQueryService,
            DiscussionRepository aDiscussionRepository,
            CollaboratorService aCollaboratorService) {
        return new ForumApplicationService(aForumQueryService,
                aForumRepository,
                aForumIdentityService,
                aDiscussionQueryService,
                aDiscussionRepository,
                aCollaboratorService);
    }

    @Bean
    public ForumQueryService forumQueryService(NamedParameterJdbcTemplate jdbcTemplate) {
        return new ForumQueryService(jdbcTemplate);
    }

    @Bean
    public PostApplicationService postApplicationService(
            PostRepository aPostRepository,
            ForumRepository aForumRepository,
            CollaboratorService aCollaboratorService) {
        return new PostApplicationService(aPostRepository,
                aForumRepository,
                aCollaboratorService);
    }

    @Bean
    public PostQueryService postQueryService(NamedParameterJdbcTemplate jdbcTemplate) {
        return new PostQueryService(jdbcTemplate);
    }

    @Bean
    public DomainRegistry domainRegistry() {
        return new DomainRegistry();
    }

    @Bean
    public FollowStoreEventDispatcher followStoreEventDispatcher(
            EventStore eventStore,
            NamedParameterJdbcTemplate jdbcTemplate) {
        return new FollowStoreEventDispatcher(eventStore, jdbcTemplate);
    }

    @Bean
    public SpringRabbitMQEventDispatcher springRabbitMQEventDispatcher(
            RabbitTemplate rabbitTemplate,
            FollowStoreEventDispatcher followStoreEventDispatcher) {
        return new SpringRabbitMQEventDispatcher(rabbitTemplate,
                Exchanges.COLLABORATION_EXCHANGE_NAME,
                followStoreEventDispatcher);
    }

    @Bean
    public MySQLProjectionDispatcher mySQLProjectionDispatcher(
            FollowStoreEventDispatcher followStoreEventDispatcher) {
        return new MySQLProjectionDispatcher(followStoreEventDispatcher);
    }

    @Bean
    public MySQLCalendarEntryProjection mySQLCalendarEntryProjection(
            MySQLProjectionDispatcher mySQLProjectionDispatcher,
            NamedParameterJdbcTemplate jdbcTemplate) {
        return new MySQLCalendarEntryProjection(mySQLProjectionDispatcher, jdbcTemplate);
    }

    @Bean
    public MySQLCalendarProjection mySQLCalendarProjection(
            MySQLProjectionDispatcher mySQLProjectionDispatcher,
            NamedParameterJdbcTemplate jdbcTemplate) {
        return new MySQLCalendarProjection(mySQLProjectionDispatcher,
                jdbcTemplate);
    }

    @Bean
    public MySQLDiscussionProjection mySQLDiscussionProjection(
            MySQLProjectionDispatcher mySQLProjectionDispatcher,
            NamedParameterJdbcTemplate jdbcTemplate) {
        return new MySQLDiscussionProjection(mySQLProjectionDispatcher,
                jdbcTemplate);
    }

    @Bean
    public MySQLForumProjection mySQLForumProjection(
            MySQLProjectionDispatcher mySQLProjectionDispatcher,
            NamedParameterJdbcTemplate jdbcTemplate) {
        return new MySQLForumProjection(mySQLProjectionDispatcher,
                jdbcTemplate);
    }

    @Bean
    public MySQLPostProjection mySQLPostProjection(
            MySQLProjectionDispatcher mySQLProjectionDispatcher,
            NamedParameterJdbcTemplate jdbcTemplate) {
        return new MySQLPostProjection(mySQLProjectionDispatcher,
                jdbcTemplate);
    }

}
